package com.vip.zuul.configuration;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClientSpecification;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerContext;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;
import org.springframework.stereotype.Component;

import com.netflix.client.ClientFactory;
import com.netflix.client.IClient;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.DummyPing;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.PingUrl;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import com.vip.zuul.service.IVipRouteService;
import com.vip.zuul.vo.RouteDTO;
import com.vip.zuul.vo.RouteReqVO;

/**
 * Description : 
 * @Author Evan Liu
 * @Date 2018年11月12日
 */
public class VIPZuulRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator{

    public final static Logger logger = LoggerFactory.getLogger(VIPZuulRouteLocator.class);

    @Autowired
    private ZuulProperties properties;
    
    @Autowired
    private SpringClientFactory springClientFactory;
    
    @Autowired
    private IVipRouteService vipZuulService;
    
    public VIPZuulRouteLocator(String servletPath, ZuulProperties properties,SpringClientFactory springClientFactory) {
        super(servletPath, properties);
        this.properties = properties;
        this.springClientFactory = springClientFactory;
        logger.info("servletPath:{}",servletPath);
    }

    /*//父类已经提供了这个方法，这里写出来只是为了说明这一个方法很重要！！！
    @Override
    protected void doRefresh() {
        super.doRefresh();
    }*/

    @Override
    public void refresh() {
        doRefresh();
    }

    @Override
    protected Map<String, ZuulRoute> locateRoutes() {
        LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
        //从application.properties中加载路由信息
        routesMap.putAll(super.locateRoutes());
        //从db中加载路由信息
        routesMap.putAll(locateRoutesFromDB());
        //优化一下配置
        LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
        for (Map.Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
            String path = entry.getKey();
            // Prepend with slash if not already present.
            if (!path.startsWith("/")) {
                path = "/" + path;
            }
            if (StringUtils.isNotEmpty(this.properties.getPrefix())) {
                path = this.properties.getPrefix() + path;
                if (!path.startsWith("/")) {
                    path = "/" + path;
                }
            }
            values.put(path, entry.getValue());
        }
        return values;
    }

    private Map<String, ZuulRoute> locateRoutesFromDB(){
        Map<String, ZuulRoute> routes = new LinkedHashMap<>();
        List<RouteDTO> results = vipZuulService.getRouteRules();
        for (RouteDTO dto : results) {
            if(StringUtils.isBlank(dto.getZuulRoute().getPath())){
                continue;
            }
            ZuulRoute zuulRoute = new ZuulRoute();
            try {
                BeanUtils.copyProperties(dto,zuulRoute);
            } catch (Exception e) {
                logger.error("=============load zuul route info from db with error==============",e);
            }
            //添加路由设置Path 或 ServiceID
            routes.put(zuulRoute.getPath(),zuulRoute);
        }
        return routes;
    }
    
    public void configRibbonWithServiceID() {
    	List<RouteDTO> results = vipZuulService.getRouteRules();
    	for (RouteDTO vipRule : results) {
			if(StringUtils.isNotEmpty(vipRule.getZuulRoute().getId())) {
				String serviceId = vipRule.getZuulRoute().getId();
				String newRuleClassName = vipRule.getBalancerName();
				String servers = vipRule.getServers();
				if(StringUtils.isNotEmpty(servers)) {
					List<String> listServers = Arrays.asList(StringUtils.split(servers, ","));
					//RibbonClientConfigurationRegistrar  Registrar;
					//BeanDefinitionRegistry registry = ApplicationUtils.getApplicationContext().getBean(SimpleBeanDefinitionRegistry.class);
					//configRibbonBeanDefinition(registry,serviceId,new FooConfiguration());
					configRibbonClient(serviceId);
					//AnnotationConfigApplicationContext	context = ApplicationUtils.getApplicationContext().getBean(AnnotationConfigApplicationContext.class);
					//context.registerBean(serviceId, RibbonClient.class);
					configRibbonClientBalancer(serviceId,newRuleClassName,listServers,vipRule.getPingUrl());
				}
			}
		}
    }
    
    //判断是否存在RibbonClient配置
    private void configRibbonClient(String serviceName) {
    	//springClientFactory --> NamedContextFactory<RibbonClientSpecification>
    	Set<String> namedContext = springClientFactory.getContextNames();
    	if(!namedContext.contains(serviceName)) {
    		List<RibbonClientSpecification> configurations = new ArrayList<>();
    		RibbonClientSpecification  specification = new RibbonClientSpecification();
    		specification.setName(serviceName);
    		specification.setConfiguration(new Class[] {RibbonClientConfiguration.class});
    		configurations.add(specification);
    		springClientFactory.setConfigurations(configurations);
    	}
		//AnnotationConfigApplicationContext	annotationContext = ApplicationUtils.getApplicationContext().getBean(AnnotationConfigApplicationContext.class);
		//annotationContext.registerBean(name + ".RibbonClientSpecification", RibbonClient.class);
    }
    
    /**
     * Change loadbalancer's serverList configuration
     */
    @SuppressWarnings({"rawtypes", "unchecked" })
	private void configRibbonClientBalancer(String serviceId, String newRuleClassName, List<String> servers,String pingUrl) {
    	IClient iClient = ClientFactory.getNamedClient(serviceId);
    	IClientConfig iClientConfig = springClientFactory.getClientConfig(serviceId);
    	RibbonLoadBalancerContext iLoadBalancerContext = springClientFactory.getLoadBalancerContext(serviceId);
    	ILoadBalancer  iLoadBalancer  = springClientFactory.getLoadBalancer(serviceId);
        if(iLoadBalancer != null) {
        	// 1.Reset listOfServer content if listOfServers changed
    		DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer)iLoadBalancer; 
            List<String> newServerList = new ArrayList<>();
            servers.forEach(temp -> {
                temp = temp.trim();
                newServerList.add(temp);
            });
            List<Server> oldServerList = loadBalancer.getServerListImpl().getUpdatedListOfServers();
            // Judge the listOfServers changed or not
            boolean serversChanged = false;
            if(oldServerList != null) {
                if(oldServerList.size() != newServerList.size()) {
                    serversChanged = true;
                } else {
                    for(Server temp : oldServerList) {
                        if(!newServerList.contains(temp.getId())) {
                            serversChanged = true;
                            break;
                        }
                    }
                }
            } else {
                serversChanged = true;
            }
            
            // listOfServers has changed
            if(serversChanged) {
            	logger.info("ServiceId: {} has changed listOfServers, new: {}", serviceId, Arrays.toString(servers.toArray()));
                loadBalancer.setServerListImpl(new ServerList<Server>() {
                    @Override
                    public List<Server> getInitialListOfServers() {
                        return null;
                    }

                    @Override
                    public List<Server> getUpdatedListOfServers() {
                        List<Server> newServerConfigList = new ArrayList<>();
                        newServerList.forEach(temp -> {
                            Server server = new Server(temp);
                            newServerConfigList.add(server);
                        });
                        // Using the new config listOfServers
                        return newServerConfigList;
                    }
                });
            }

            // 2. Reset loadBalancer rule
            if(loadBalancer.getRule() != null) {
                String existRuleClassName = loadBalancer.getRule().getClass().getName();
                if(!newRuleClassName.equals(existRuleClassName)) {
                	logger.info("ServiceId: {}, Old rule class: {}, New rule class: {}", serviceId, existRuleClassName, newRuleClassName);
                    configLoadBalancerRule(newRuleClassName, loadBalancer);
                }
            } else {
                configLoadBalancerRule(newRuleClassName, loadBalancer);
                logger.info("ServiceId: {}, Old rule class: Null, Need rule class: {}", serviceId, newRuleClassName);
            }
            
            // 3. Reset loadBalancer ping
            if(StringUtils.isNotEmpty(pingUrl)) {
            	IPing ping =  new PingUrl(false,pingUrl);
            	loadBalancer.setPing(ping);
            }else {
            	IPing ping = new DummyPing();
            	loadBalancer.setPing(ping);
            }
        }
    }
    
    /**
     * Change loadBalancer's rule
     * @param ruleClassName
     * @param loadBalancer
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void configLoadBalancerRule(String ruleClassName, DynamicServerListLoadBalancer loadBalancer) {
        try {
            // Create specify class instance
			Class clazz = Class.forName(ruleClassName);
			Constructor constructor = clazz.getConstructor();
            IRule rule = (IRule) constructor.newInstance();
            // Bind loadBalancer with this Rule
            rule.setLoadBalancer(loadBalancer);
            loadBalancer.setRule(rule);
        } catch (Exception e) {
        	logger.error(e.getMessage(), e);
        }
    }
}

